/*
 * 版权所有 (C) 2015 知启蒙(ZHIQIM) 保留所有权利。[遇见知启蒙，邂逅框架梦]
 * 
 * https://zhiqim.org/project/zhiqim_framework/zhiqim_httpd.htm
 *
 * Zhiqim Httpd is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *          http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 */
package org.zhiqim.httpd;

import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.zhiqim.httpd.model.HttpDomainPortPath;
import org.zhiqim.kernel.util.Asserts;
import org.zhiqim.kernel.util.Validates;

/**
 * HTTP响应类
 *
 * @version v1.0.0 @author zouzhigang 2014-3-21 新建与整理
 */
public class HttpResponseImpl implements HttpResponse
{
    private HttpRequestAbs request;
    private HttpSenderImpl sender;
    private HttpContext context;
    private List<HttpCookie> cookies;
    
    private boolean addSessionIdToCookie;
    private boolean addCookieToSender;

    public HttpResponseImpl(HttpRequestAbs request)
    {
        this.request = request;
        this.sender = (HttpSenderImpl)request.getSender();
        this.context = request.getContext();
        
        this.cookies = new ArrayList<HttpCookie>();
    }
    
    /***********************************************************************/
    // 关联连接信息
    /***********************************************************************/
    
    /** 获取连接编号 */
    public String getId()
    {
        return request.getId();
    }
    
    /** 获取当前请求 */
    public HttpRequest getRequest()
    {
        return request;
    }
    
    /** 获取上下文环境 */
    public HttpContext getContext()
    {
        return context;
    }
    
    /***********************************************************************/
    // 状态处理和判断
    /***********************************************************************/
    
    /** 是否可编辑 */
    public boolean isEditable()
    {
        return request != null && request.isEditable();
    }
    
    /** 是否已提交 */
    public boolean isCommitted()
    {
        return request == null || request.isCommitted();
    }
    
    /***********************************************************************/
    // 设置状态或，或重定向，或错误重定向
    /***********************************************************************/

    /**
     * 单独设置状态，通过setStatus,write和commit完成操作
     */
    public void setStatus(int code)
    {
        sender.setStatus(code);
    }
    
    /**
     * 发生错误，返回错误码和标准的错误信息
     * 
     * @param code          错误码
     * @throws IOException  可能的异常
     */
    public void sendError(int code) throws IOException
    {
        sendError(code, null);
    }
    
    /**
     * 发生错误，返回错误状态和一句错误描述，如果描述为null，则取标准错误描述
     * 
     * @param code              错误码
     * @param message           错误信息
     * @throws IOException      可能的异常
     */
    public void sendError(int code, String message) throws IOException
    {        
        sender.sendError(code, message);
    }
    
    /**
     * 发送错误信息，内容为HTML格式
     * 
     * @param code              响应码
     * @throws IOException      可能的异常
     */
    public void sendErrorHTML(int code) throws IOException
    {
        sender.sendErrorHTML(code);
    }

    /**
     * 发送错误信息，内容为HTML格式
     * 
     * @param code              响应码
     * @param reason            响应原因
     * @throws IOException      可能的异常
     */
    public void sendErrorHTML(int code, String reason) throws IOException
    {
        sender.sendErrorHTML(code, reason);
    }
    
    /**
     * 客户端重定向，将资源指身一个永久URI，采用302把URI写在location消息头中
     * 
     * @param location          重定向连接地址
     * @throws IOException      可能的异常
     */
    public void sendRedirect(String location) throws IOException
    {
        Asserts.asState(!isCommitted()?null:"已提交不允许再提交");
        
        this.addCookieSessionId();
        this.addCookieToSender();
        sender.sendRedirect(encodeURL(location));
    }
    
    /**
     * 只返回消息头，如304等，会清空内容
     * 
     * @param code              响应码，可以是200表示成功
     * @throws IOException      可能的异常
     */
    public void sendHeader(int code) throws IOException
    {
        this.addCookieSessionId();
        this.addCookieToSender();
        sender.sendHeader(code);
    }
    
    /**
     * 一般用于返回200-207的成功码和提示的一个内容，和sendError不同的是sendError的reason=content，而sendSuccess的reason=默认值
     *  
     * @param code              成功码
     * @param content           内容
     * @throws IOException      可能的异常
     */
    public void sendContent(int code, String content) throws IOException
    {
        this.addCookieSessionId();
        this.addCookieToSender();
        sender.sendContent(code, content);
    }
    
    /** 编码URL，增加非useCookie时有可能的jsessionid，在URL跳转时需要 */
    public String encodeURL(String url)
    {
        if (context == null)
            return url;
        
        HttpSessionManager sessionManager = context.getSessionManager();
        if (sessionManager == null)
            return url;
        
        if (context.isCookieUse())
            return url;
        
        HttpSession session = getRequest().getSession();
        if (session == null)
            return url;
        
        String sessionId = session.getId();
        if (sessionId == null)
            return url;
        
        int ind = url.indexOf("?");
        if (ind == -1)
            url = url + "?"+sessionManager.getSessionIdName()+"=" + sessionId;
        else//存在参数
            url = url + "&"+sessionManager.getSessionIdName()+"=" + sessionId;
        return url;
    }
    
    /** 获取响应状态码 */
    public int getStatus()
    {
        return sender.getStatus();
    }
    
    /** 获取状态描述 */
    public String getReason()
    {
        return sender.getReason();
    } 
    
    /***********************************************************************/
    // 设置头部信息
    /***********************************************************************/
    
    /** 设置头部域 */
    public void setHeader(String key, Object value)
    {
        sender.setHeader(key, value);
    }
    
    /** 设置头部日期格式域 */
    public void setDateHeader(String key, long value)
    {
        sender.setDateHeader(key, value);
    }
    
    /** 设置缓存控制域 */
    public void setCacheControl(String value)
    {
        setHeader(_CACHE_CONTROL_, value);
    }
    
    /** 设置缓存控制域为私有 */
    public void setCacheControlPrivate()
    {
        setHeader(_PRAGMA_, _PRIVATE_);
        setHeader(_CACHE_CONTROL_, _PRIVATE_);
    }
    
    /** 设置头部域，增加一个属性 */
    public void addHeader(String key, Object value)
    {
        sender.addHeader(key, value);
    }
    
    /** 设置头部日期格式域，增加一个日期类型的属性 */
    public void addDateHeader(String key, long value)
    {
        sender.addDateHeader(key, value);
    }
    
    /** 删除头部域 */
    public void removeHeader(String key)
    {
        sender.removeHeader(key);
    }
    
    /** 获取头部域 */
    public String getHeader(String key)
    {
        return sender.getHeader(key);
    }
    
    /** 判断是否有头部域 */
    public boolean hasHeader(String key)
    {
        return sender.hasHeader(key);
    }
    
    /** 设置编码格式 */
    public void setCharacterEncoding(String encoding)
    {
        sender.setCharacterEncoding(encoding);
    }
    
    /** 获取编码格式 */
    public String getCharacterEncoding()
    {
        String encoding = sender.getCharacterEncoding();
        if (Validates.isEmptyBlank(encoding))
            encoding = getRequest().getCharacterEncoding();
        
        return Validates.isEmptyBlank(encoding)?_UTF_8_:encoding;
    }
    
    /**
     * 设置内容类型，会增加默认的字符集到消息头中
     * 
     * @param contentType       内容类型
     */
    public void setContentType(String contentType)
    {
        sender.setContentType(contentType);
    }
    
    /**
     * 设置内容类型，不设置字符集到消息头中
     * 
     * @param contentType       内容类型
     */
    public void setContentTypeNoCharset(String contentType)
    {
        sender.setContentTypeNoCharset(contentType);
    }
    
    /** 获取内容长度 */
    public long getFlushLength()
    {
        return sender.getFlushLength();
    }
    
    /***********************************************************************/
    // 设置Cookie
    /***********************************************************************/
    
    /** 增加cookie值，浏览器关闭即失效 */
    public void addCookie(String name, String value)
    {
        cookies.add(new HttpCookie(name, value));
    }
    /**
     * 增加cookie值
     * 
     * @param name      名称
     * @param value     值
     * @param seconds   存活时间，单位：秒
     */
    public void addCookie(String name, String value, int seconds)
    {
        HttpCookie cookie = new HttpCookie(name, value);
        cookie.setMaxAge(seconds);
        
        addCookie(cookie);
    }
    
    /** 增加Cookie值 */
    public void addCookie(HttpCookie cookie)
    {
        cookies.add(cookie);
    }
    
    /** 删除Cookie值 */
    public void removeCookie(String name)
    {
        for (Iterator<HttpCookie> it = cookies.iterator();it.hasNext();)
        {
            HttpCookie cookie = it.next();
            if (name.equals(cookie.getName()))
                it.remove();
        }
        
        addCookie(name, null, -1);
    }
    
    /** 清空Cookie */
    public void clearCookie()
    {
        cookies.clear();
    }
    
    /** 把cookie写入到sender */
    private void addCookieToSender()
    {
        if (addCookieToSender || cookies.isEmpty() || context == null || !context.isCookieUse())
            return;

        String sessionIdName = context.getSessionManager().getSessionIdName();
        HttpDomainPortPath dpp = getRequest().getDomainPortPath();
        String prefix = dpp.buildCookiePrefix(context.isCookieForce());
        
        for (HttpCookie cookie : cookies)
        {
            sender.addMultiHeader(_SET_COOKIE_, cookie.getResponseValue(dpp.getDomain(), dpp.getPort(), dpp.getPath(), cookie.isSessionIdName(sessionIdName)?null:prefix));
        }
        
        addCookieToSender = true;
    }
    
    /** 把sessionId组装成cookie */
    private void addCookieSessionId()
    {
        if (addSessionIdToCookie || context == null || !context.isCookieUse())
            return;
        
        HttpSessionManager sessionManager = context.getSessionManager();
        if (sessionManager == null)
            return;
        
        HttpSession session = request.getSession();
        if (session == null)
            return;
        
        //有会话要求维持时，放置到Cookies中
        String sessionId = session.getId();
        addCookie(new HttpCookie(sessionManager.getSessionIdName(), sessionId));
            
        //如果有浸入式修改了sessionId,则清理老会话，设置新会话
        String oldSessionId = session.getOldSessionId();
        if (oldSessionId != null)
        {
            if (!sessionId.equals(oldSessionId))
                sessionManager.invalidateSession(oldSessionId);
            session.clearOldSessionId();
            sessionManager.setSession(session);
        }
        
        addSessionIdToCookie = true;
    }
    
    /***********************************************************************/
    // 数据流处理，包括写入、打印和提交等
    /***********************************************************************/
    
    /** 获取输出流 */
    public OutputStream getOutputStream()
    {
        return sender.getOutputStream();
    }
    
    /** 写内容字节方式 */
    public void write(byte[] b) throws IOException
    {
        addCookieSessionId();
        addCookieToSender();
        
        sender.write(b);
    }
    
    /** 写内容加回车换行 */
    public void println(String str) throws IOException
    {
        addCookieSessionId();
        addCookieToSender();
        
        sender.println(str);
    }
    
    /** 写回车换行 */
    public void println() throws IOException
    {
        addCookieSessionId();
        addCookieToSender();
        
        sender.println();
    }
    
    /** 写内容,无回车换行 */
    public void print(String str) throws IOException
    {
        addCookieSessionId();
        addCookieToSender();
        
        sender.print(str);
    }
    
    /** 提交响应 */
    public void commit() throws IOException
    {
        if (isCommitted())
            return;

        addCookieSessionId();
        addCookieToSender();
        sender.commit();
    }

    /***********************************************************************/
    // toString
    /***********************************************************************/
    
    public String toString()
    {
        return sender.toString();
    }
    
    /***********************************************************************/
    // 内部调用的方法，不通过HttpResponse公开
    /***********************************************************************/
    
    /** 销毁 */
    void destroy()
    {
        if (cookies != null)
        {
            cookies.clear();
            cookies = null;
        }
        
        //引用置空
        request = null;
        sender = null;
        context = null;
    }
    
    byte[] buildChunkedHeader(boolean chunked)
    {
        return sender.buildChunkedHeader(chunked);
    }
}
